package com.ollobot.authorization.service;

import com.ollobot.authorization.entity.EmailVerificationCode;
import com.ollobot.authorization.exception.EmailSendException;
import com.ollobot.authorization.exception.RateBeyondException;
import com.ollobot.authorization.repository.EmailVerificationCodeRepository;

import io.micrometer.common.util.StringUtils;
import jakarta.mail.AuthenticationFailedException;
import jakarta.mail.MessagingException;
import jakarta.mail.SendFailedException;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.exceptions.TemplateProcessingException;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
@RequiredArgsConstructor
@Slf4j
public class EmailVerificationService {

    private final EmailVerificationCodeRepository codeRepository;
    private final JavaMailSender mailSender;
    private final SecureRandom secureRandom = new SecureRandom();
    private final RedisTemplate<String,String> redisTemplate;
    private final TemplateEngine templateEngine;


    private static final String CODE_PREFIX = "vc:code:";
    private static final String COUNT_PREFIX = "vc:count:";
    
    // 1分钟内最大发送次数
    private static final int MAX_ATTEMPTS = 3;
    // 发送频率限制时间（秒）
    private static final long RATE_LIMIT_SECONDS = 60;
    // 验证码有效期（秒）
    private static final long CODE_EXPIRATION_SECONDS = 180;
    
    // 邮箱格式验证正则表达式
    private static final Pattern EMAIL_PATTERN = Pattern.compile(
        "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
    );
    
    // 常见的无效或测试邮箱域名
    private static final Pattern INVALID_DOMAIN_PATTERN = Pattern.compile(
        ".*\\.(test|example|invalid|localhost|local)$", Pattern.CASE_INSENSITIVE
    );

    @Value("${spring.mail.username}")
    private String emailFrom;

    @Transactional
    public String generateAndSendCode(String email) {
        
        // 预验证邮箱格式和域名
        validateEmailAddress(email);
        checkRateLimit(email);

        // 生成6位数字验证码
        String code = String.format("%06d", secureRandom.nextInt(1000000));

        // 先发送邮件，如果失败则不保存验证码
        sendVerificationEmail(email, code);

        // 邮件发送成功后才保存验证码和增加计数
        String codeKey = CODE_PREFIX + email;
        redisTemplate.opsForValue().set(
                codeKey,
                code,
                CODE_EXPIRATION_SECONDS,
                TimeUnit.SECONDS);

        incrementSendCount(email);
        
        log.info("Generated and sent verification code for email: {}", email);
        return code;
    }

    public void sendVerificationEmail(String email, String code) {
        try {
            // 渲染HTML内容
            Context context = new Context();
            context.setVariable("code", code);
            context.setVariable("expiration", CODE_EXPIRATION_SECONDS/60);

            String emailContent;
            try {
                emailContent = templateEngine.process("verification-email", context);
            } catch (TemplateProcessingException e) {
                log.error("Template processing failed for email: {}", email, e);
                throw new EmailSendException(
                    "邮件模板处理失败", 
                    email, 
                    EmailSendException.ErrorType.TEMPLATE_ERROR, 
                    e
                );
            }

            // 创建邮件消息
            MimeMessage mimeMessage = mailSender.createMimeMessage();
            MimeMessageHelper helper;
            
            try {
                helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
                helper.setTo(email);
                helper.setFrom(emailFrom);
                helper.setSubject("OlloBot 登录验证码");
                helper.setText(emailContent, true); // true表示HTML内容
            } catch (AddressException e) {
                log.error("Invalid email address: {}", email, e);
                throw new EmailSendException(
                    "邮箱地址格式无效: " + email, 
                    email, 
                    EmailSendException.ErrorType.INVALID_EMAIL, 
                    e
                );
            } catch (MessagingException e) {
                log.error("Message creation failed for email: {}", email, e);
                throw new EmailSendException(
                    "邮件消息创建失败", 
                    email, 
                    EmailSendException.ErrorType.UNKNOWN_ERROR, 
                    e
                );
            }

            // 发送邮件
            try {
                mailSender.send(mimeMessage);
                log.info("Verification email sent successfully to: {}", email);
            } catch (MailAuthenticationException e) {
                log.error("SMTP authentication failed for email: {}", email, e);
                throw new EmailSendException(
                    "SMTP服务器认证失败", 
                    email, 
                    EmailSendException.ErrorType.AUTHENTICATION_FAILED, 
                    e
                );
            } catch (MailSendException e) {
                // 检查是否是邮箱地址不可达
                Throwable cause = e.getCause();
                if (cause instanceof SendFailedException) {
                    SendFailedException sendFailed = (SendFailedException) cause;
                    if (sendFailed.getInvalidAddresses() != null && sendFailed.getInvalidAddresses().length > 0) {
                        log.error("Email address not reachable: {}", email, e);
                        throw new EmailSendException(
                            "邮箱地址不可达，请检查邮箱地址是否正确: " + email, 
                            email, 
                            EmailSendException.ErrorType.EMAIL_NOT_REACHABLE, 
                            e
                        );
                    }
                }
                
                // 检查网络相关错误
                if (cause instanceof ConnectException || 
                    cause instanceof SocketTimeoutException || 
                    cause instanceof UnknownHostException) {
                    log.error("Network error when sending email to: {}", email, e);
                    throw new EmailSendException(
                        "网络连接失败，请稍后重试", 
                        email, 
                        EmailSendException.ErrorType.NETWORK_ERROR, 
                        e
                    );
                }
                
                log.error("SMTP error when sending email to: {}", email, e);
                throw new EmailSendException(
                    "SMTP服务器错误: " + e.getMessage(), 
                    email, 
                    EmailSendException.ErrorType.SMTP_ERROR, 
                    e
                );
            } catch (MailException e) {
                log.error("Mail service error for email: {}", email, e);
                throw new EmailSendException(
                    "邮件服务错误: " + e.getMessage(), 
                    email, 
                    EmailSendException.ErrorType.SMTP_ERROR, 
                    e
                );
            }
            
        } catch (EmailSendException e) {
            // 重新抛出EmailSendException
            throw e;
        } catch (Exception e) {
            log.error("Unexpected error when sending email to: {}", email, e);
            throw new EmailSendException(
                "发送邮件时发生未知错误: " + e.getMessage(), 
                email, 
                EmailSendException.ErrorType.UNKNOWN_ERROR, 
                e
            );
        }
    }

    @Transactional
    public boolean verifyCode(String email, String code) {

        String codeKey = CODE_PREFIX + email;
        String storedCode = redisTemplate.opsForValue().get(codeKey);

        if (storedCode == null) {
            return false; // 验证码不存在或已过期
        }

        if (storedCode.equals(code)) {
            // 验证成功，删除验证码（保留计数直到过期）
            redisTemplate.delete(codeKey);
            return true;
        }

        log.warn("Email verification failed for: {} with code: {}", email, code);
        return false;
    }

    public boolean hasValidCode(String email) {
        Optional<EmailVerificationCode> code = 
            codeRepository.findLatestValidCodeByEmail(email, LocalDateTime.now());
        return code.isPresent();
    }

    @Transactional
    public void cleanupExpiredCodes() {
        codeRepository.deleteExpiredCodes(LocalDateTime.now());
        log.info("Cleaned up expired verification codes");
    }



    private void checkRateLimit(String target) {
        String countKey = COUNT_PREFIX + target;
        
        // 获取当前发送次数
        String countStr = redisTemplate.opsForValue().get(countKey);
        int count = countStr != null ? Integer.parseInt(countStr) : 0;
        
        if (count >= MAX_ATTEMPTS) {
            // 获取剩余限制时间
            Long expire = redisTemplate.getExpire(countKey, TimeUnit.SECONDS);
            if (expire == null || expire < 0) {
                // 如果计数器没有过期时间，设置过期时间并重新获取
                redisTemplate.expire(countKey, RATE_LIMIT_SECONDS, TimeUnit.SECONDS);
                expire = redisTemplate.getExpire(countKey, TimeUnit.SECONDS);
            }
            
            if (expire != null && expire > 0) {
                throw new RateBeyondException(expire);
            }
        }
    }


     private void incrementSendCount(String target) {
        String countKey = COUNT_PREFIX + target;
        
        // 使用原子操作增加计数
        Long count = redisTemplate.opsForValue().increment(countKey);
        
        // 如果是第一次设置，添加过期时间
        if (count != null && count == 1) {
            redisTemplate.expire(countKey, RATE_LIMIT_SECONDS, TimeUnit.SECONDS);
        }
    }

    /**
     * 验证邮箱地址格式和域名
     * @param email 邮箱地址
     * @throws EmailSendException 如果邮箱地址无效
     */
    private void validateEmailAddress(String email) {
        // 基本格式验证
        if (!EMAIL_PATTERN.matcher(email).matches()) {
            log.warn("Invalid email format: {}", email);
            throw new EmailSendException(
                "邮箱地址格式无效: " + email,
                email,
                EmailSendException.ErrorType.INVALID_EMAIL
            );
        }
        
        // 检查是否是测试或无效域名
        if (INVALID_DOMAIN_PATTERN.matcher(email).matches()) {
            log.warn("Invalid email domain: {}", email);
            throw new EmailSendException(
                "不支持的邮箱域名: " + email,
                email,
                EmailSendException.ErrorType.INVALID_EMAIL
            );
        }
        
        // 检查一些明显的无效邮箱模式
        String domain = email.substring(email.indexOf('@') + 1).toLowerCase();
        
        // 检查是否包含明显的测试或无效关键词
        if (domain.contains("test") || domain.contains("fake") || 
            domain.contains("invalid") || domain.contains("dummy") ||
            domain.startsWith("temp") || domain.startsWith("disposable")) {
            log.warn("Suspicious email domain: {}", email);
            throw new EmailSendException(
                "不支持的邮箱域名: " + email,
                email,
                EmailSendException.ErrorType.INVALID_EMAIL
            );
        }
        
        log.debug("Email address validation passed for: {}", email);
    }

}
